#ifndef MEMREF_H
#define MEMREF_H

#include <fstream>
#include <string>
#include <cassert>
#include <memory>
#include <stdint.h>
#include <stdio.h>
#include <iostream>

#define unlikely(x) __builtin_expect(!!(x), 0)

//Memory reference record(refered to as MEMREF hereafter).
//POD type.
struct MEMREF {
  //There are now four types of threadlet boundaries, more may be added later.
  enum BoundaryType {
	ENTER_CS = 1,
	LEAVE_CS =2,
	ENTER_PS = 3,
	LEAVE_PS = 4,
  };
  void *pc;	//Instruction that makes this MEMREF.
  void *addr; //Memory address.
  size_t size;//Access width.
  void *lock; //Lock address when in critial section(CS), NULL otherwise.
  int read;   //Read(1), write(0).
  uint32_t tid;//Thread that makes this MEMREF.
};  //Sizeof(MEMREF) == 40

struct MA {
  MA() = default;
  MA(uint64_t s, uint64_t e, bool read) : start(s), mode_end(e),
  writes_before(0) {
	mode_end |= read ? (1UL << 63) : 0;
  }

  uint64_t getEnd() const {
	return mode_end & ~(1UL << 63);
  }

  void setEnd(uint64_t e) {
	bool read = getMode();
	mode_end = e;
	mode_end |= read ? (1UL << 63) : 0;
  }

  // Return reference.
  uint64_t const &getStart() const { return start; }
  uint64_t &getStart() { return start; }

  uint32_t const &getWritesBefore() const { return writes_before; }
  uint32_t &getWritesBefore() { return writes_before; }

  size_t getWidth() const { return getEnd() - getStart(); }

  bool getMode() const {
	return (mode_end >> 63) ? true : false;
  }

  bool overlapWith(MA const &that) const {
	return getStart() < that.getEnd() && getEnd() > that.getStart();
  }
  bool empty() const {
	return getStart() >= getEnd();
  }

  // Operators.
  MA &operator=(MA const &that) {
	if (this != &that) {
	  start = that.start;
	  mode_end = that.mode_end;
	}
	return *this;
  }

  bool operator<(MA const &that) const {
	return getEnd() <= that.getStart();
  }
  bool operator>(MA const &that) const {
	return that.getEnd() <= getStart();
  }

  uint64_t start;
  uint64_t mode_end; // Mode + end address.
  uint32_t writes_before;
};

inline bool StartCmp(MA const &left, MA const &right) {
  return left.getStart() < right.getStart();
}

inline
std::ostream &operator<<(std::ostream &os, const MA &ma) {
  printf("%-15p %u %-5s", (void *)ma.getStart(), (unsigned int)ma.getWidth(),
	  ma.getMode()  ? "READ" : "WRITE");
  return os;
}

//Read/Write binary to log file.
bool GetBinaryEntry(std::ifstream &log_file, std::unique_ptr<char[]> &buf);

inline bool IsBinaryBoundary(const MA &ma) {
  return (uint64_t)ma.start <= MEMREF::LEAVE_PS;
}

inline bool IsBinaryCSBoundary(const MA &ma) {
  return (uint64_t)ma.start < MEMREF::ENTER_PS;
}

inline bool IsBinaryPSBoundary(const MA &ma) {
  return (uint64_t)ma.start > MEMREF::LEAVE_CS && (uint64_t)ma.start <= MEMREF::LEAVE_PS;
}

inline void PutBinaryMEMREF(std::ofstream &log_file, const MEMREF &ref) {
  assert(log_file.is_open());
  MA ma((uint64_t)ref.addr, (uint64_t)ref.addr + ref.size, ref.read);
  log_file.write((const char *)&ma, sizeof(MA));
}

extern void PutBinaryBoundary(std::ofstream &log_file, MEMREF::BoundaryType bType,
	const std::string &data);

//Read/Write text to log file.
inline void PutTextMEMREF(std::ofstream &log_file, const std::string &txt) {
  assert(log_file.is_open());
  log_file << txt;
}

extern void PutTextBoundary(std::ofstream &log_file, MEMREF::BoundaryType bType,
	const std::string &data);

#endif
